package no.ffi.gismoidm.attrex;

import java.util.Properties;
import java.util.StringTokenizer;

/**
 * Class with methods that parses a string representation in to a BooleanOperand object
 * @author anders
 */
public class Parser {
    
    static StringTokenizer stok;

    public static BooleanOperand parse(String rep) throws Exception {
        if (rep==null) return new TrueAlways();
        stok = new StringTokenizer(rep," (),",true);
        // Enter into a recursive call method, first we expect an operand
        BooleanOperand oper = (BooleanOperand)dispatch(nextToken(null));
        if (stok.hasMoreTokens()) 
            throw new AttributeEvaluationException("Superfluous tokens at the end of expression");
        return oper;
    }
    
    private static String nextToken(String expected) throws Exception {
        try {
            String token = stok.nextToken();
            if (expected!=null && !token.equals(expected)) 
               throw new Exception("Expected token \"" + expected + "\" found \"" + token + "\"");
            return token;
        } catch (java.util.NoSuchElementException e) {
            String message = "Unexpected end of expression";
            if (expected!=null)
                message += " while expecting \"" + expected + "\"";
            throw new Exception(message);
        }
    }
    
    private static Operand dispatch(String token) throws Exception {
        if (token.startsWith("$")) return new AttributeOperand(token.substring(1));
        if (token.startsWith("#")) return new IntegerOperand(token.substring(1));
        if (token.startsWith("\"") && token.endsWith("\"")) 
            return new StringOperand(token.substring(1,token.length()-1));
        if (token.equals("and")) return and();
        if (token.equals("or")) return or();
        if (token.equals("eq")) return eq();
        if (token.equals("gt")) return gt();
        if (token.equals("not")) return not();
        if (token.equals("exists")) return nameExists();
        if (token.equals("contains")) return contains();
        if (token.equals("hastoken")) return hasToken();
        if (token.equals("startswith")) return startsWith();
        throw new Exception("Token: " + token + " does not make sense");
    }
    
    private static BooleanOperand and() throws Exception {
        nextToken("(");
        BooleanOperand leftOp = (BooleanOperand)dispatch(nextToken(null));
        nextToken(",");
        BooleanOperand rightOp = (BooleanOperand)dispatch(nextToken(null));
        nextToken(")");
        return new And(leftOp,rightOp);
    }
    
    private static BooleanOperand or() throws Exception {
        nextToken("(");
        BooleanOperand leftOp = (BooleanOperand)dispatch(nextToken(null));
        nextToken(",");
        BooleanOperand rightOp = (BooleanOperand)dispatch(nextToken(null));
        nextToken(")");
        return new Or(leftOp,rightOp);
    }

    private static BooleanOperand eq() throws Exception {
        nextToken("(");
        AttributeOperand leftOp = (AttributeOperand)dispatch(nextToken(null));
        nextToken(",");
        Operand rightOp = dispatch(nextToken(null));
        nextToken(")");
        return new Equals(leftOp,rightOp);
    }
    
    private static BooleanOperand not() throws Exception {
        nextToken("(");
        BooleanOperand op = (BooleanOperand)dispatch(nextToken(null));
        nextToken(")");
        return new Not(op);
    }
    
    private static BooleanOperand nameExists() throws Exception {
        nextToken("(");
        AttributeOperand op = (AttributeOperand)dispatch(nextToken(null));
        nextToken(")");
        return new NameExists(op);
    }
    
    private static BooleanOperand gt() throws Exception {
        nextToken("(");
        AttributeOperand leftOp = (AttributeOperand)dispatch(nextToken(null));
        nextToken(",");
        NumberOperand rightOp = (NumberOperand)dispatch(nextToken(null));
        nextToken(")");
        return new GreaterThan(leftOp,rightOp);
    }
    
    private static BooleanOperand contains() throws Exception {
        nextToken("(");
        AttributeOperand leftOp = (AttributeOperand)dispatch(nextToken(null));
        nextToken(",");
        StringOperand rightOp = (StringOperand)dispatch(nextToken(null));
        nextToken(")");
        return new Contains(leftOp,rightOp);
    }
    
    private static BooleanOperand startsWith() throws Exception {
        nextToken("(");
        AttributeOperand leftOp = (AttributeOperand)dispatch(nextToken(null));
        nextToken(",");
        StringOperand rightOp = (StringOperand)dispatch(nextToken(null));
        nextToken(")");
        return new Startstring(leftOp,rightOp);
    }

    private static BooleanOperand hasToken() throws Exception {
        nextToken("(");
        AttributeOperand leftOp = (AttributeOperand)dispatch(nextToken(null));
        nextToken(",");
        StringOperand rightOp = (StringOperand)dispatch(nextToken(null));
        nextToken(")");
        return new HasToken(leftOp,rightOp);
    }

    static String example1 = "exists($clearance)";
    //static String example1 = "and(eq($clearance,#444),not(eq($nationality,\"italy\")))";
    static String example2 = "and(gt($salary,#450000),or(hastoken($roles,\"hitman\"),contains($office,\"B01\")))";
    static String example3 = "and(eq($CLEARANCE,\"secret\"),not(gt($IS_OVERDUE,#10)))";

    public static void main(String[] args) throws Exception {
        
        BooleanOperand op1 = (BooleanOperand)parse(example1);
        System.out.println(op1);
        BooleanOperand op2 = (BooleanOperand)parse(example3);
        System.out.println(op2);
        BooleanOperand op3 = (BooleanOperand)parse(op2.toString());
        System.out.println(op3);
        
        Properties prop = new Properties();
        prop.setProperty("clearance", "444 ,666,777");
        prop.setProperty("nationality", "norwegian");
        prop.setProperty("salary"," 490000");
        prop.setProperty("roles", "auditor, himan, boss");
        prop.setProperty("office","B01-243");
        
        prop.list(System.out);
        BooleanValue bv = op1.getBooleanValue(prop);
        System.out.println(bv);
        
    }
    
}
